var App = App || {};

(function(NS){
	'use strict';
	
	function EventMgr(){};
	
	EventMgr.debug = false;
	if (document.location.hash.indexOf('debug') > -1)
	{
		EventMgr.debug = true;
	}
	EventMgr.topics = new Object();
	EventMgr.overrides = new Object();
	EventMgr.mappedEvents = new Object();
	
	EventMgr.bindToDomEvent = function(domEvent, domElement){
		// Handle default domElement target
		if ( domElement == undefined){
			domElement = window;
		}
		// Handle as string or array
		if (typeof(domEvent) == 'string'){
			domEvent = [domEvent];
		}
		
		var numEvents = domEvent.length;
		for (var i=0;i < numEvents;i++){
			var eventName = domEvent[i]; 
			this.mappedEvents[eventName] = [];
			domElement.addEventListener(eventName,function(eventObj){
   				EventMgr.fireMappedEvent(eventName,eventObj);
			});
			// Map DOM events to topics subscribers
			this.mapDomEventToPublish(eventName, domElement.id + '-' + eventName);
	   }

		// IE8 needs to bind key events at document not window level
		/*
		if ((domEvent == 'keypress' || domEvent == 'keydown'  || domEvent == 'keyup')) // from utils
		{
			domElement = document;
		}
		*/
	}
	
	EventMgr.unbindDomEvent = function( domEvent, domElement ){
		// Handle default domElement target
		if ( domElement == undefined){
			domElement = window;
		}
		// Handle as string or array
		if (typeof(domEvent) == 'string'){
			domEvent = [domEvent];
		}
		
		var numEvents = domEvent.length;
		for (var i=0;i < numEvents;i++){
			var eventName = domEvent[i]; 
			// Map DOM events to topics subscribers
			this.mapDomEventToPublish(eventName, domElement.id + '-' + eventName);

			domElement.addEventListener(eventName, (function(eventName){
				return function(e){
					EventMgr.fireMappedEvent(eventName,e);
				}
			})(eventName));
	   }
	}
	
	/**
	 * maps a bound dom event to an event we will publish
	 * @param String domEvent name
	 * @param String publishedEvent name
	 * @private
	 */
	EventMgr.mapDomEventToPublish = function(eventList,publishedEvent){
		
		if (typeof(eventList) == 'string'){
			eventList = [eventList];
		}
		
		for (var i=0;i<eventList.length;i++){
			var domEvent = eventList[i];
			if (! this.mappedEvents[domEvent]){
				this.bindToDomEvent(domEvent);
			}
			// Make sure we don't already have the event mapped
			var found = false;
			for (var evt in this.mappedEvents){
				if (evt == domEvent){
					for (var j=0;j< this.mappedEvents[evt].length;j++){
						if ( this.mappedEvents[evt][j] == publishedEvent){
							found = true;
							break;
						}
					}
					break;
				}
			}
			if (!found){
				this.mappedEvents[domEvent].push(publishedEvent);
			}
		}
		return true;
	}
	
	
	EventMgr.fireMappedEvent = function(map, evt){
		var thisMap = this.mappedEvents[map];
		if (thisMap){
			var numSubscribers = thisMap.length;
			for (var i=0; i < numSubscribers; i++){
				var domEvent = thisMap[i];
				if (this.debug){
					console.log('dom:'+map+' as '+domEvent);
				}
				this.publish(domEvent,{evt:evt});
			}
		}
	}

	/**
	 * publishes a state change to any registrars
	 * @param {String} evt event to trigger
	 * @param {Object} data json data to hand off
	 * @private
	 */
	EventMgr.publish = function( evt, data ){
		
		
		if (this.overrides[evt]){
			// override callbacks will return true to keep override
			if (this.overrides[evt].callback.apply( data.context, data )){
				return;
			}
		}
		if (typeof(this.topics[evt]) == 'undefined'){
			return;
		}
		
		var numSubscribers = this.topics[evt].length;
		for(var i=0; i < numSubscribers; i++){
			if (this.topics[evt][i] && this.topics[evt][i].callback ){

				var subscriber = this.topics[evt][i],
					callback = subscriber.callback;
				var context = typeof(subscriber.context !== undefined) ? subscriber.context : this;

				if (this.debug){
					console.log(evt+' to '+ subscriber.name);
				}
				
				callback.apply( context, [data] );
			}
		}
	}
	
	EventMgr.subscribeOverride = function(evt,name,callback,context){
		if (this.overrides[evt]){
			return false;
		}
	   var eventData = {'name':name,'callback':callback};
		if ( context !== undefined ){
			eventData.context = context;
		}
		this.overrides[evt] = eventData;
		return true;
	}
	
	EventMgr.unsubscribeOverride = function(evt,name){
		delete this.overrides[evt];
	}
		/**
		 * allows for other systems to subscribe and hear when we have a state change
		 * @param {String} event to watch for
		 * @param {String} name of registrar
		 * @param {Function} callback to call when triggered
		 * @private
		 */
	EventMgr.subscribe = function(eventList,name,context,callback){
	   if (typeof(eventList) == 'string'){
		   eventList = [eventList];
	   }
	   for (var i=0;i<eventList.length;i++){
		   var evt = eventList[i];
		   if (!name || name == ""){
			   console.log('ERROR: subscription to '+evt+' requires a name. You used "'+name+'"');
			   return;
		   }
		   if (typeof(this.topics[evt]) == 'undefined'){
			   this.topics[evt] = [];
		   }
		   
		   var eventData = {'name':name,'callback':callback};
		   if ( context !== undefined ){
			   eventData.context = context;
		   }
		   this.topics[evt].push(eventData);
	   }
	}
	/**
	 * allows for other systems to unsubscribe
	 * @param {String} event to watch for
	 * @param {String} name of registrar
	 * @private
	 */
	EventMgr.unsubscribe = function(evt,name){
		if ((typeof(this.topics[evt]) == 'undefined') || (typeof name == 'undefined')){
			return;
		}
		var newArray = [];
		for(var i=0;i<this.topics[evt].length;i++){
			if (this.topics[evt][i].name != name){
				newArray.push(this.topics[evt][i]);
			}
		}
		this.topics[evt] = newArray;
	}

	/**
	 * public access to the overrides of any of our events
	 * @param {String} evtName
	 * @public
	 */
	EventMgr.dumpOverrides = function(evt){
		return this.overrides[evt] || []
	};

	/**
	 * public access to the dom events bound to our events
	 * @param {String} evtName
	 * @public
	 */
	EventMgr.dumpDomEvents = function(){
		var retval = [];
		for(var key in this.mappedEvents)
		{
			retval.push(key);
		}
		return retval;
	};
	/**
	 * public access to our events
	 * @param {String} evtName
	 * @public
	 */
	EventMgr.dumpEvents = function(){
		var retval = [];
		for(var key in this.topics)
		{
			retval.push(key);
		}
		return retval;
	};
	/**
	 * public access to the subscribers of dom events bound to our events
	 * @param {String} evtName
	 * @public
	 */
	EventMgr.dumpDomSubscribers = function(evt)
	{
		return this.dumpSubscribers(this.dumpDomEvents(evt));
	};
	/**
	 * public access to the subscribers of any of our events
	 * @param {String} evtName
	 * @public
	 */
	EventMgr.dumpSubscribers = function(evt){
		return this.topics[evt]
	};
	/**
	 * public access to our events
	 * @param {String} evtName
	 * @public
	 */
	EventMgr.dumpEverything = function(){
		return this.topics;
	};
	
	NS.Events = EventMgr;
	
})(App);